package loppy.math 
{
  import loppy.loppy_internal;
  import loppy.utils.ObjectPool;
	
	public final class Vec2
  {
    use namespace loppy_internal;
    
    loppy_internal static const pool:ObjectPool = new ObjectPool(Vec2);
    
		public var x:Number;
		public var y:Number;
		
		public function Vec2(x:Number = 0.0, y:Number = 0.0)
    {
			this.x = x;
			this.y = y;
		}
		
		public function clone():Vec2
    {
			return new Vec2(x, y);
		}
    public function copy(from:Vec2):Vec2
    {
      return set(from.x, from.y);
    }
    
		public function dot(rhs:Vec2):Number
    {
			return (x * rhs.x) + (y * rhs.y);
		}
    public function cross(rhs:Vec2):Number
    {
      return x * rhs.y - y * rhs.x;
    }
    
    public function addThis(rhs:Vec2):Vec2
    {
      x += rhs.x;
      y += rhs.y;
      return this;
    }
    public function add(rhs:Vec2):Vec2
    {
      return clone().addThis(rhs);
    }
    
    public function subThis(rhs:Vec2):Vec2
    {
      x -= rhs.x;
      y -= rhs.y;
      return this;
    }
    public function sub(rhs:Vec2):Vec2
    {
      return clone().subThis(rhs);
    }
    
    public function mulThis(scalar:Number):Vec2
    {
      x *= scalar;
      y *= scalar;
      return this;
    }
    public function mul(scalar:Number):Vec2
    {
      return clone().mulThis(scalar);
    }
    
    public function negateThis():Vec2
    {
      x = -x;
      y = -y;
      return this;
    }
    public function negate():Vec2
    {
      return clone().negateThis();
    }
		
		public function project(target:Vec2):Vec2
    {
			return clone().projectThis(target);
		}
		public function projectThis(target:Vec2):Vec2
    {
      var d:Number = dot(target);
			copy(target).mulThis(d / target.lengthSQ());
      return this;
		}
    public function reflectThis(target:Vec2):Vec2
    {
      var temp:Vec2 = pool.get();
      temp.set(x, y).projectThis(target);
      x += 2.0 * (temp.x - x);
      y += 2.0 * (temp.y - y);
      pool.put(temp);
      return this;
    }
		
    public function rotate(angle:Number):Vec2
    {
			return clone().rotateThis(angle);
		}
		public function rotateThis(radians:Number):Vec2
    {
			var originalX:Number = x;
			x = originalX * Math.cos(radians) - y * Math.sin(radians);
			y = originalX * Math.sin(radians) + y * Math.cos(radians);
      return this;
		}
    
    public function rotateCcwRightAngle():Vec2
    {
      return clone().rotateCcwRightAngleThis();
    }
    public function rotateCcwRightAngleThis():Vec2
    {
      var originalX:Number = x;
      x = -y; y = originalX;
      return this;
    }
    public function rotateCwRightAngle():Vec2
    {
      return clone().rotateCwRightAngleThis();
    }
    public function rotateCwRightAngleThis():Vec2
    {
      var originalX:Number = x;
      x = y; y = -originalX;
      return this;
    }
    
    public function invert():Vec2
    {
      return clone().invertThis();
    }
    public function invertThis():Vec2
    {
      x = -x; y = -y;
      return this;
    }
    public function invertX():Vec2
    {
      return clone().invertXThis();
    }
    public function invertXThis():Vec2
    {
      x = -x;
      return this;
    }
    public function invertY():Vec2
    {
      return clone().invertYThis();
    }
    public function invertYThis():Vec2
    {
      y = -y;
      return this;
    }
    
    public function normalize():Vec2
    {
      return clone().normalizeThis();
    }
    public function normalizeThis():Vec2
    {
      var length_inv:Number = 1.0 / length();
      x *= length_inv;
      y *= length_inv;
      return this;
    }
		
		public function length():Number
    {
			return Math.sqrt(x * x + y * y);
		}
		public function setLength(value:Number):Vec2
    {
			var factor:Number = value / length();
			x *= factor;
			y *= factor;
      return this;
		}
    public function lengthSQ():Number
    {
      return x * x + y * y;
    }
		
		public function set(x:Number, y:Number):Vec2
    {
			this.x = x;
			this.y = y;
      return this;
		}
		
		public function angle():Number { return Math.atan2(y, x); }
		public function setAngle(radians:Number):Vec2
    {
			var originalLength:Number = length();
			x = originalLength * Math.cos(radians);
			y = originalLength * Math.sin(radians);
      return this;
		}
		
		public function toString():String
    {
			return "[Vec2" + " x=" + x + " y=" + y + "]";
		}
	}
}
